#ifndef TMS_DB_H_
#define TMS_DB_H_

#pragma once

#include <fstream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <string>
#include <stdexcept>
#include <algorithm>
#include <exception>
#include "tms_definitions.hpp"

#ifdef _TMS_DB_TEST
#include <iostream>
#endif

#ifdef TMS_WIN32_PLATFORM
#include <cstddef>
#endif

// 定义一个域，用来表示一个二维字符向量，储存数据库表
typedef std::vector<std::vector<std::string>> field;

/*
基本数据库
这是一个基本数据库实现，部分功能参考了数据库，但是显然缺少相应的功能性和可靠性
最显著的不同是不提供每个子句的列选择和排序方法，因为数据库的实现方式是字符串
同样由于字符串实现和作者精力限制，没有指定储存数据类型和主键的功能
数据库在读取时直接全部读入内存，也没有使用磁盘节约没存空间和复杂的排序实现
这里可能让你失望，看起来这个数据库实现似乎没什么用，但是他可以快速使用 sqlite 相近的接口，可以战未来
DB类使用 CSV 文件作为数据库持久储存的方式，在内存中使用二维字符向量实现数据存储
*/
class BaseDBAccessior
{
private:
    std::string file_name;
    field data;

    int load_CSV();
    int write_CSV() const;

    BaseDBAccessior(const BaseDBAccessior &);

protected:
public:
    /*
    构造函数，直接读入整个数据库到内存，并且检查数据库的基本合规性，
    该方法在读取文件不成功时抛出 runtime_error，
    如果调用时产生异常，则数据库不会被加载，应该销毁对象，并且处理错误后再次尝试
    */
    explicit BaseDBAccessior(const std::string &f);

    /*
    选择，模拟 SQL SELECT 子句
    根据列名和值选择列，返回域
    选择器支持使用 * 进行全文通配或者使用值进行精准匹配
    此方法在没有正确指定表头时抛出 invalid_argument
    在没有条目被选中时返回空值
    */
    field select(const std::string &tar_row, const std::string &selector) const;

    /*
    更新，模拟 SQL UPDATE 子句
    选择主键对应的值，然后更新相应行的值
    此方法在没有正确指定表头时抛出 invalid_argument
    返回值对应应用更改成功的行数
    */
    int update(const field &new_sub_field);

    /*
    插入，模拟 SQL INSERT 子句
    对于重复的主键不会写入，新主键的行写入
    此方法在插入时不会精确检测每一行的语义是否完全符合，只检查长度是否匹配
    在插入行长度不匹配时抛出 runtime_error
    返回值为成功写入行数
    */
    int insert(const field &new_sub_field);

    /*
    提交更改，调用方法进行持久化写入
    此方法在不能打开文件的情况下抛出 runtime_error 异常
    返回值是整个数据库的写入成功的行数
    */
    int commit() const;

    /*
    删除，模拟 SQL DELETE 子句
    使用时先想想能不能用 update + del 安全实现
    此方法先按照选择器选中行，然后删除行
    此方法在找不到对应表头时抛出 invalid_argument 异常
    返回删除的行数
    */
    int del(const std::string &tar_row, const std::string &selector);

    static std::string print2str(const field &);
};

/*
时间表类
时间表类用于程序内储存，交换时间表项目
此类可以从数据库行构造，也可以从独立的时间表参数构造，实现数据的双向交换
*/
class TimeTableItem
{
public:
    std::string time_table_id;
    std::string time_launch;
    std::string spot_launch;
    std::string spot_arrive;
    std::string duration;
    double ticket_price;
    int capacity;
    int ticket_sold_cnt;
    int is_canceled;

    TimeTableItem(const std::vector<std::string> &row);

    TimeTableItem(
        const std::string &time_table_id,
        const std::string &time_launch,
        const std::string &spot_launch,
        const std::string &spot_arrive,
        const std::string &duration,
        double ticket_price,
        int capacity,
        int ticket_sold_cnt,
        int is_canceled);
};

/*
销售数据类
销售数据类用于程序内储存，交换销售数据项目
此类可以从数据库行构造，也可以从独立的销售数据参数构造，实现数据的双向交换
*/
class SaleItem
{
public:
    std::string ticket_id;
    std::string time_id;
    std::string buyer_id;

    SaleItem(const std::vector<std::string> &row);

    SaleItem(const std::string &ticket_id, const std::string &time_id, const std::string &buyer_id);
};

/*
用户数据类
用户数据类用于程序内储存，交换用户数据项目
此类可以从数据库行构造，也可以从独立的用户数据参数构造，实现数据的双向交换
*/
class UserItem
{
public:
    std::string user_id;
    std::string name;
    std::string passwd;
    double wallet;

    UserItem(const std::vector<std::string> &row);

    UserItem(const std::string &user_id,
             const std::string &name,
             const std::string &passwd,
             double wallet);
};

/*
数据库后端
所有的高级数据库抽象和功能接口都是通过此类实现，这是整个程序最复杂的类
虽然本质上是数据库访问，但是每个函数都不直接使用 field 返回，而是根据每一个方法定制结构体

测试数据库自动生成，启用测试构建时提供的功能，会生成一个数据库用于测试

列出全部车次，接口会自动计算发车时间，并使用参数进行标记
车辆信息查询，提供两个重载，一个使用车次号，一个使用终点站，返回的都是已经排序完成的向量
增加车次，添加新的车次
注销车次，将车次标记为注销，实际上没有删除，只是调整为了另一种模式，管理更加安全

注册用户，注册新的用户，会对ID进行查重
管理员验证，验证管理员ID和密码
用户验证，验证用户的密码
用户信息修改，修改已经存在的用户的信息

购买车票，按照一定规则购买车票
退订车票，按照一定规则退订车票
*/
class TmsDataBackend
{
private:
    BaseDBAccessior *time_table_db;
    BaseDBAccessior *user_db;
    BaseDBAccessior *sales_db;

    TmsDataBackend(const TmsDataBackend &);

    /* 获取下一个售票ID */
    std::string get_next_ticket_id() const;

    /* 获取下一个时间表ID */
    std::string get_next_time_table_id() const;

    /* 辅助函数：将时间字符串转换为分钟数 */
    static inline int time_to_minutes(const std::string &time_str);

    /* 辅助比较函数：比较两个 TimeTableItem 的发车时间 */
    static inline bool compare_by_launch_time(const TimeTableItem &a, const TimeTableItem &b);

public:
    TmsDataBackend(const std::string &time_table_file, const std::string &user_file, const std::string &sales_file);

    virtual ~TmsDataBackend();

    /*
    列出所有车次
    返回一个所有车次类组成的向量，所有的此类列出和查找函数都不会抛出异常
    */
    std::vector<TimeTableItem> list_all_trains() const;

    /*
    按照 ID 查找车次
    返回一个所有车次类组成的向量
    */
    std::vector<TimeTableItem> query_train_by_id(const std::string &time_table_id) const;

    /*
    按照终点站查找车次
    返回一个所有车次类组成的向量
    */
    std::vector<TimeTableItem> query_train_by_arrive_spot(const std::string &spot_arrive) const;

    /*
    添加新车次
    添加车次不会抛出异常
    方法成功执行的返回值为 1，不会出现高于 1 的返回值，0 代表插入数据库失败
    */
    int add_train(const TimeTableItem &new_train);

    /*
    修改车次信息
    修改车次不会抛出异常
    方法成功执行的返回值为 1，不会出现高于 1 的返回值
    返回值 0 代表更新数据库失败，大概率由于修改不存在的车次引起
    */
    int modify_train(const TimeTableItem &new_train);

    /*
    取消车次
    取消车次不会抛出异常
    方法成功执行的返回值为 1，不会出现高于 1 的返回值
    返回值 0 代表插入更新数据库失败，大概率由于修改不存在的车次引起
    */
    int cancel_train(const std::string &train_id);

    /*
    删除车次
    删除车次不会抛出异常
    方法成功执行的返回值为 1，不会出现高于 1 的返回值
    返回值 0 代表插入更新数据库失败，大概率由于修改不存在的车次引起
    此方法非常危险，不能在运行结束之前使用
    */
    int del_train(const std::string &train_id);

    /*
    获取一个用户名下的购票记录，返回一个由 SaleItem 组成的向量
    此方法不会抛出异常，没有用户购票记录返回空值，往往是由于用户不存在或者从未购票
    */
    std::vector<SaleItem> get_sales_by_user(const std::string &user_id) const;

    /*
    注册新用户，使用 UserItem 作为参数，每一个成员都是有效的
    此方法在有冲突 ID 时抛出 runtime_error 异常
    成功执行应该返回 1，返回值不会大于 1，返回 0 可能是数据库访问异常
    */
    int register_user(const UserItem &new_usert);

    /*
    修改用户数据，使用 UserItem 作为参数，使用 ID 确定用户，wallet 无效
    此方法在用户 ID 不存在时抛出 runtime_error 异常
    成功执行应该返回 1，返回值不会大于 1，返回 0 可能是数据库访问异常
    */
    int modify_user(const UserItem &updated_user);

    /*
    验证用户登录
    此方法在用户不存在或者是密码不正确时都抛出 runtime_error 异常
    异常可以次用 what 成员访问具体失败原因
    */
    int verify_user(const std::string &user_id, const std::string &passwd) const;

    /*
    删除用户
    此方法不会抛出异常
    成功执行应该返回 1，返回值不会大于 1，返回 0 可能是数据库访问异常
    */
    int del_user(const std::string &user_id);

    /*
    按照ID查找用户
    如果用户存在，则返回用户 useritem 对象
    用户不存在时抛出 runtime_error 异常
    */
    UserItem find_user_by_id(const std::string &user_id) const;

    /*
    列出所有用户
    返回 useritem 组成的向量，此方法不会抛出异常
    */
    std::vector<UserItem> list_all_users() const;

    /*
    列出用户
    返回 useritem 组成的向量，此方法不会抛出异常
    */
    std::vector<UserItem> query_user_by_id(const std::string &selector) const;

    /*
    充值钱包
    此方法不会抛出异常，返回非零值时执行成功
    返回 0 时执行失败，大概率是用户不存在
    */
    int charge_user_wallet(const std::string &user_id, double charge);

    /*
    使用钱包
    此方法不会抛出异常，返回非零值时执行成功
    如果在使用时通配用户，只会生效的一个，这个行为是未定义的
    返回 0 时执行失败，大概率是用户不存在或者透支
    */
    int pay_user_wallet(const std::string &user_id, double payment);

    /*
    购票
    购票操作可能会触发多处异常，这些异常都是 runtime_error 都可以使用 what 成员获取具体原因
    车次取消后购票异常
    发车前十分钟内购票异常
    重复买票异常（就是不让你买多张）
    钱包钱不够异常
    车上没有座位异常
    */
    int purchase_ticket(const std::string &user_id, const std::string &train_id);

    /*
    退票
    退票操作可能会触发多处异常，这些异常都是 runtime_error 都可以使用 what 成员获取具体原因
    尝试退一张不存在的票异常
    退不是自己的票异常
    开车前十分钟内尝试退票异常
    */
    int refund_ticket(const std::string &user_id, const std::string &ticket_id);

    /*
    提交数据库更改
    此方法在不能写入数据库文件时抛出 runtime_error 异常
    */
    int commit_changes() const;

    /*
    导出日志文件
    导出日志文件返回文件长度
    如果返回 0 ，则为文件写入异常
    */
    int export_log_file(const std::string &fime_name);

    /* 数据格式化工具 */

    /*
    将时间表渲染为字符串，返回渲染后的字符串
    col_w 控制每列宽度
    coloring 控制是否染色时间和取消
    header 是否渲染表头
    use_csv 启用 csv 格式数据
    */
    static std::string time_table2string(const std::vector<TimeTableItem> &items, const int col_w, const int coloring, const int header = 1, const int use_csv = 0);

    /* 销售条目打印到字符串，col_w 参数控制每列宽度，返回渲染后的字符串 */
    static std::string sales2string(const std::vector<SaleItem> &items, const int col_w);

    /* 用户书记打印到字符串，col_w 参数控制每列宽度，返回渲染后的字符串 */
    static std::string user2string(const std::vector<UserItem> &items, const int col_w);

    /* 得到当前时间字符串 */
    static std::string get_current_time();

    /* 根据时间换算背景颜色 */
    static std::string get_time_status_bg_color(const std::string &time_launch);
};

#endif
